﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace Muel.TypeScriptFacade.Core
{
    public class TypeScriptClass : TypeScriptType
    {
        public TypeScriptClass(TypeScriptServiceManager manager, Type type) : base(manager, type) { }

        private bool initialised = false;
        internal override void InitialiseInheritance()
        {
            if (!initialised)
            {
                // initialise base type
                if (type.BaseType != null && !type.BaseType.IsUnderlyingPrimitive())
                {
                    this.BaseType = manager.ObjectManager.Create(type.BaseType);
                }
                else
                {
                    this.BaseType = null;
                }

                // initialise any types inheriting from this
                if (!type.IsUnderlyingPrimitive())
                {
                    this.ChildTypes = type.GetSubTypes()
                        .Where(t => t.HasAttributeOfType<DataContractAttribute>())
                        .Select(t => manager.ObjectManager.Create(t))
                        .ToArray();
                }
                else
                {
                    this.ChildTypes = new TypeScriptType[0];
                }
            }
        }

        #region Output to TypeScript

        /// <summary>
        /// {0}: Modifier
        /// {1}: Class name
        /// {2}: Extends class
        /// {3}: .NET type
        /// {4}: Class properties
        /// {5}: super(data) call
        /// {6}: constructor code
        /// {7}: change tracking code (see changeTrackingFormat)
        /// </summary>
        private const string classFormat = @"
    export {0}class {1} {2}{{
        public __type: string = ""{3}"";
{4}
        constructor(data?: any) {{
{5}
            if (data == null) {{ data = {{}}; }}
{6}
        }}
{7}
    }}";

        /// <summary>
        /// {0} snapshot method name
        /// {1} snapshot code
        /// {2} hasChanges method name
        /// {3} hasChanges code
        /// </summary>
        private const string changeTrackingFormat = @"
        public {0}(): void {{
{1}
        }}
        public {2}(): boolean {{
{3}
            return false;
        }}
        public {4}(): void {{
{5}
        }}";

        internal override string ToTypeScript()
        {
            // {2}: Class properties
            string propertiesCode = string.Join(
                Environment.NewLine, OwnMembers
                    .Select(property => property.ToTypeScriptProperty())
                    .ToArray());
            
            // {3}: constructor code
            var constructorMembers = OwnMembers
                    .Where(property => !property.MemberType.IsGenericParameter)
                    .Select(property => property.ToTypeScriptConstructor());
            
            // look in the base type to see if any generic arguments were passed, to see if any instantiators 
            // for inherited members should be included in the constructor code
            if (this.BaseType != null && this.BaseType.IsGeneric)
            {
                var baseGenericArguments = this.BaseType.GenericParameters.Where(param => !param.Type.IsGenericParameter).ToArray();
                constructorMembers = constructorMembers.Concat(this.InheritedMembers.Where(member => baseGenericArguments.Contains(member.MemberType)).Select(member => member.ToTypeScriptConstructor()));
            }
            
            string constructorCode = string.Join(
                Environment.NewLine, constructorMembers.ToArray());

            // {4}: change tracking code
            string changeTrackingCode = string.Empty;
            if (manager.Settings.ChangeTracking.Enabled)
            {
                // {0}: snapshot method name
                string snapshotName = manager.Settings.ChangeTracking.SnapshotMethodName;

                // {1}: snapshot code
                string snapshotCode = (this.BaseType != null) ? string.Format("super.{0}();{1}", snapshotName, Environment.NewLine) : string.Empty;
                snapshotCode += string.Join(
                    Environment.NewLine, OwnMembers
                        .Select(property => property.ToTypeScriptSnapshot())
                        .ToArray());

                // {2}: hasChanges method name
                string hasChangesName = manager.Settings.ChangeTracking.HasChangesMethodName;

                // {3}: hasChanges code
                string hasChangesCode = (this.BaseType != null) ? string.Format("if (super.{0}()) {{ return true; }}{1}", hasChangesName, Environment.NewLine) : string.Empty;
                hasChangesCode += string.Join(
                    Environment.NewLine, OwnMembers
                        .Select(property => property.ToTypeScriptHasChanges())
                        .ToArray());

                // {4}: revert method name
                string revertName = manager.Settings.ChangeTracking.RevertMethodName;

                // {5}: revert code
                string revertCode = (this.BaseType != null) ? string.Format("super.{0}();{1}", revertName, Environment.NewLine) : string.Empty;
                revertCode += string.Join(
                    Environment.NewLine, OwnMembers
                        .Select(property => property.ToTypeScriptRevert())
                        .ToArray());

                changeTrackingCode = string.Format(changeTrackingFormat,
                    snapshotName,
                    snapshotCode,
                    hasChangesName,
                    hasChangesCode,
                    revertName,
                    revertCode);
            }

            return string.Format(classFormat,
                (this.IsAbstract) ? "abstract " : "",
                this.TypeScriptClassName,
                (this.BaseType != null) ? "extends " + this.BaseType.GetTypeScriptType() + " " : string.Empty,
                this.GetTypeProperty(),
                propertiesCode,
                (this.BaseType != null) ? "super(data);" : string.Empty,
                constructorCode,
                changeTrackingCode);
        }

        public override bool CanSnapshot
        {
            get
            {
                string typeScriptPropertyType = GetTypeScriptType();
                switch (typeScriptPropertyType)
                {
                    case "string":
                    case "string[]":
                    case "number":
                    case "number[]":
                    case "boolean":
                    case "boolean[]":
                    case "any":
                        return true;
                    default:
                        return false;
                }
            }
        }
        #endregion


        /// <summary>
        /// Gets the __type property expected to be returned by the serialiser
        /// </summary>
        /// <returns></returns>
        internal string GetTypeProperty()
        {
            if (this.manager.Settings.ServiceProvider == Configuration.ServiceProviderEnumeration.AspNetWebService)
            {
                return this.FullName;
            }
            else
            {
                return string.Format("{0}:#{1}", this.Name, this.Namespace);
            }
        }

    }
}
